#ifndef SCHEDULER_HH
#define SCHEDULER_HH

#include <set>
#include "uMap.hh"
#include "sync_queue.hh"
#include <sys/time.h>
#include <signal.h>
#include<iostream>

class uScheduler {
	
public:
	
	uScheduler() : running_id(0), thread_counter(0), sleeper_id(-1), delete_after(NULL) {
		//create thread0
		uThread *t0=new uThread(thread_counter++, NULL);
		this->threads.insert(t0->get_id(), t0);
		ready_queue.insert(t0);
	}
	
	virtual ~uScheduler() {
		for(uMap<int,uThread*>::iterator it = threads.begin(); it!=threads.end(); ++it)
			delete it->second;
		
		threads.clear();
	}
	
	void initializeSignals(void (*sighandler)(int)) {
		struct sigaction act;
		act.sa_handler = sighandler;
		if(sigemptyset(&act.sa_mask)==-1)
			throw "system error: Error emptying signal mask\n";
		act.sa_flags = 0;
		act.sa_restorer = NULL;
		if(sigaction(SIGVTALRM, &act, NULL)==-1)
			throw "system error: Error initializing signals\n";
		//signal(SIGVTALRM, timer_handler);
	}
	
	void timer_handler(int sig)
	{
		if(!this->threads.contains(sleeper_id)) {
			return;
		}
		uThread *wokeUp = this->threads[sleeper_id];
		ready_queue.erase(wokeUp);
		
		if(wokeUp->get_sync()==NULL) wokeUp->set_state(uThread::READY);
		
		ready_queue.insert(wokeUp);
		sleeper_id=-1;
		run_next_thread();
	};
	
	
	void sleep_thread(int thread_id,int num_millisec) {
			if(num_millisec<=0) 
				throw "thread library error: no negative sleep time.";
			if(thread_id==0) 
				throw "thread library error: main can't sleep";
			if(this->sleeper_id!=-1) 
				throw "thread library error: Another thread is sleeping already";
			if(!this->threads.contains(thread_id)) {
				throw "thread library error: Thread not found.";
			}
			uThread *t = this->threads[thread_id];

			if(t->get_state() == uThread::SUSPENDED)
				throw "thread library error: Thread is already suspended.";
			
			ready_queue.erase(t);

			struct itimerval tv;
			tv.it_value.tv_sec = num_millisec/1000; //time of first timer
			tv.it_value.tv_usec = num_millisec%1000 * 1000; //time of first timer
			tv.it_interval.tv_sec = 0; //time of all timers but the first one
			tv.it_interval.tv_usec = 0; //time of all timers but the first one
			t->set_state(uThread::SUSPENDED);
			this->sleeper_id=thread_id;
			ready_queue.insert(t);
			if (setitimer(ITIMER_VIRTUAL, &tv, NULL) != 0)
				std::cout << "set timer";
			//setitimer(ITIMER_VIRTUAL, &tv, NULL);
			//t->sleep(millisec); //TODO: tell the thread to sleep
			run_next_thread();

			//TODO: set the wakeup call

		};

	
	void sync_thread(int sync_id, int num) {
		
		if(num<2)
			throw "thread library error: Sync only allowed with num >= 2";
		
		int thread_id = this->get_current_thread();
		if(thread_id==0) 
			throw "thread library error: can't put main to sleep!";
		uThread* t=this->threads[thread_id];
		
		Sync_queue* sq=NULL;

		//the sync point doesn't exist yet. create it
		if(this->sync_map.contains(sync_id)) {
			sq=sync_map[sync_id];
			if(sq->get_num() != num) // sync point was already declared with different num
				throw "thread library error: This sync_id is registered with another num";
		} else {
			sq=new Sync_queue(sync_id,num);
			this->sync_map.insert(sync_id,sq);
		}
		
		
		if(this->sync_map[sync_id]->get_size() == num-1) { // queue is full, release all threads. This can't happen because num >= 2
			//std::cout << "READY QUEUE" << std::endl;
			//sq->print_vectors();
			for(std::vector<uThread*>::iterator it=sq->begin(); it!=sq->end(); ++it) {
				ready_queue.erase(*it);
				(*it)->set_sync(NULL);
				if((*it)->get_id()!=this->sleeper_id) (*it)->set_state(uThread::READY);
				ready_queue.insert(*it);
			}
			delete this->sync_map[sync_id];
			this->sync_map.erase(sync_id);
		} else {
			//change the state.
			ready_queue.erase(t);
			t->set_state(uThread::SUSPENDED);
			t->set_sync(sq);
			ready_queue.insert(t);
			sq->add_thread(t);
		}
		//print_ready_queue();
		this->run_next_thread();
	}
	
	void print_ready_queue() const {
		for(std::set<uThread*,uThreadPCompare>::const_iterator it=ready_queue.begin(); it!=ready_queue.end(); ++it)
			std::cout << "Thread:" << (*it)->get_id() << ", status:" << (*it)->get_state() << std::endl;
		std::cout << std::endl;
	}
	
	int get_current_thread() const {
		return running_id;
	}
	
	int spawn_thread(void (*thread_func)(void)) {
			uThread *t=new uThread(thread_counter++, thread_func);
			this->threads.insert(t->get_id(), t);
			ready_queue.insert(t);
			return t->get_id();
	};

	void blockSignals()
	{
		sigset_t sigset;
		if(sigemptyset(&sigset)==-1) throw "system error: Couldn't empty mask\n";
		if(sigaddset(&sigset,SIGVTALRM)==-1) throw "system error: Couldn't add signal to mask\n";
		if(sigprocmask(SIG_BLOCK,&sigset,NULL)) throw "system error: Couldn't set the process mask\n";
	}

	void UnBlockSignals()
	{
		sigset_t sigset;
		if(sigemptyset(&sigset)==-1) throw "system error: Couldn't empty mask\n";
		if(sigaddset(&sigset,SIGVTALRM)==-1) throw "system error: Couldn't add signal to mask\n";
		if(sigprocmask(SIG_UNBLOCK,&sigset,NULL)==-1) throw "system error: Couldn't set the process mask\n";

	}
	void run_next_thread() {
		
		if(ready_queue.size()==0)
			throw "thread library error: Your thread queue is empty! this should never happen!";
		
		uThread* newt = *ready_queue.begin();
		if(newt->get_state()==uThread::RUNNING)  {
			return; //we're already running the correct thread
		} else if(newt->get_state()!=uThread::READY) {
			throw "There are no READY threads. This is a bug!";
		}
		
		if(threads.contains(running_id)) {
			uThread* oldt = threads[running_id];
			if(oldt->set_bookmark()) {
				//if we return from changing the thread no need to rest this stuff
				if(delete_after!=NULL) {
					delete delete_after;
					delete_after = NULL;
				}
				return;
			}
			
			if(oldt->get_state()==uThread::RUNNING) oldt->set_state(uThread::READY);
			ready_queue.erase(oldt);
			ready_queue.insert(oldt);
		}

		ready_queue.erase(newt);
		newt->set_state(uThread::RUNNING);
		ready_queue.insert(newt);
		
		
		running_id=newt->get_id();
		newt->run_thread();
		
		return;
	};
	
	/**
	 * This function is more complicated than I would like because it requires that we take special measures if the thread is currently running.
	 */
	int terminate_thread(int tid) {
		if(!threads.contains(tid))
			throw "thread library error: Invalid thread id";
		
		uThread* t=threads[tid];
		ready_queue.erase(t);
		threads.erase(tid);
		
		Sync_queue* sq=t->get_sync();
		if(sq!=NULL) {
			sq->remove_thread(t);
			if(sq->get_size()==0) {
				sync_map.erase(sq->get_id());
				delete sq;
			}
		}
		
		if(sleeper_id==t->get_id())
			sleeper_id=-1;
		
		if(tid==running_id) {
			delete_after=t;
			run_next_thread();
		} else {
			delete t;
		}
		return 0;
	}
	
	int getThreadsWaiting(int sync_id) {
		if(!this->sync_map.contains(sync_id)) return 0;
		return this->sync_map[sync_id]->get_size();
	}
	
	int get_sleeping_id() const {
		return sleeper_id;
	}
	
	bool contains_thread(int tid) const {
		return this->threads.contains(tid);
	}

private:
	uMap<int, uThread*> threads;  // id -> thread
	std::set<uThread*,uThreadPCompare> ready_queue;  // sorted by priority
	uMap<int, Sync_queue*> sync_map; // sync_id -> sync_queue //TODO: redefine the value
	int running_id;
	int thread_counter;
	int sleeper_id;  // nobody is sleeping
	uThread* delete_after;
	
	uThread* get_thread(int thread_id) {
		if(!this->threads.contains(thread_id)) {
			throw "thread library error: Thread not found.";
		}
		return this->threads[thread_id];
	};
};

#endif
